/**
  Comun interpreter/compiler/transpiler/CLI tool.

  by drummyfish, released under CC0 1.0, public domain
*/

#ifndef BYTECODEDMEM_SIZE
  #define BYTECODEDMEM_SIZE 131072
#endif

#ifndef SYMBOLMEM_SIZE
  #define SYMBOLMEM_SIZE 16384
#endif

#ifndef INTERPRETERMEM_SIZE
  #define INTERPRETERMEM_SIZE 131072
#endif

#ifndef MIN_CELLS
  #define MIN_CELLS 128
#endif

#define CMN_STRING_PSEUDOHASH_SIZE 32

#include <stdio.h>
#include "comun.h"

#define INCLUDE_STACK_SIZE 32

#define MAX_ARGS      32

#define MODE_RUN      0  // interpret
#define MODE_BC       1  // compile to bytecode
#define MODE_BC_TEXT  2  // compile to text bytecode
#define MODE_C        3  // compile to C
#define MODE_COMUN    4  // compile to comun
#define MODE_ESTIMATE 5  // estimate resources
#define MODE_PREPROC1 6  // output first stage of preprocessing
#define MODE_PREPROC2 7  // outputs second stage of preprocessing
#define MODE_MEASURE  8  // run and measure code
#define MODE_MINIFY   9  // minimize code
#define MODE_BEAUTIFY 10 // beautify code

#define O1 CMN_OPTIMIZE_REMOVE_NOPS
#define O2 (O1 | CMN_OPTIMIZE_REPLACE_OPS)
#define O3 CMN_OPTIMIZE_ALL

#define INCLUDE_FILENAME_MAX_LEN 16
#define INCLUDE_LIST_LEN 128

// include stack:
char includeStackNames[INCLUDE_STACK_SIZE][INCLUDE_FILENAME_MAX_LEN];
FILE *includeStackFiles[INCLUDE_STACK_SIZE]; 
unsigned int includeStackLines[INCLUDE_STACK_SIZE];
int8_t includeStackTop = -1;

// list of already included files, to prevent repeated inclusions:
char includeListNameHashes[INCLUDE_LIST_LEN][CMN_STRING_PSEUDOHASH_SIZE];
int includeListLast = -1;

uint8_t includeOK = 1;
uint8_t fileJustOpened = 0;
  
const char *sourceFile = 0;
uint8_t mode = MODE_RUN;
uint8_t preprocess = 0;
uint8_t preprocessorMinify = 1;
uint8_t stackMemMultiplier = 1;

uint8_t bytecodeMem[BYTECODEDMEM_SIZE];
uint8_t preprocBytecodeMem[BYTECODEDMEM_SIZE];
uint8_t interpreterMem[INTERPRETERMEM_SIZE];
char symbolMem[SYMBOLMEM_SIZE];
char _tmpName[CMN_STRING_PSEUDOHASH_SIZE + 1];

int lastPreprocChar = -1;

CMN_Compiler compiler;
CMN_Preprocessor preprocessor;
CMN_Interpreter interpreter;

#define ERROR_PREFIX "ERROR: "
#define WARNING_PREFIX "WARNING: "
#define DEBUG_PREFIX "DEBUG: "

void printError(const char *str)
{
  fprintf(stderr,ERROR_PREFIX "%s\n",str);
}

void printDebug(const char *str)
{
  fprintf(stderr,DEBUG_PREFIX "%s\n",str);
}

// Interpreter I/O function for normal interpreting.
int16_t ioFunc(int16_t x)
{
  if (x < 0)
    return getchar();
  else
    putchar(x);

  return 0;
}

// Interpreter I/O function for preprocessing.
int16_t preprocIoFunc(int16_t x)
{
  if (x >= 0)
    lastPreprocChar = x;

  return 0;
}

// Preprocessor function that feeds output to compiler (1st pass).
void preprocessorToCompiler(char c)
{
  CMN_compilerFeedChar(&compiler,c);
}

// Preprocessor function that prints its output out (2nd pass).
void preprocessorToOut(char c)
{
  putchar(c);
}

void interpreterDebugPrint(void)
{
  char instrStr[16];

  CMN_instrToStr(interpreter.currentInstruction,instrStr);

  fprintf(stderr,DEBUG_PREFIX "%u; %04x %s call st.: %02u, ",interpreter.step,
    (unsigned int) (interpreter.currentInstruction - interpreter.bytecode - 
      CMN_BYTECODE_HEADER_SIZE) / 2,instrStr,interpreter.callStackTop);

#define _MEM(m,mm,n)\
  ((int32_t) interpreter.pointers[m][0]) >= n ? \
  interpreter.memory ## mm [interpreter.pointers[m][0] - n] : 0

  if (interpreter.memory0 != 0)
    fprintf(stderr,"mem0: %04x %04x %04x<(%u) ",
      _MEM(0,0,2),_MEM(0,0,1),_MEM(0,0,0),interpreter.pointers[0][0]);

  if (interpreter.memory8 != 0)
    fprintf(stderr,"mem8: %02x %02x %02x<(%u) ",
      _MEM(1,8,2),_MEM(1,8,1),_MEM(1,8,0),interpreter.pointers[1][0]);

  if (interpreter.memory16 != 0)
    fprintf(stderr,"mem16: %04x %04x %04x<(%u) ",
      _MEM(2,16,2),_MEM(2,16,1),_MEM(2,16,0),interpreter.pointers[2][0]);

  if (interpreter.memory32 != 0)
    fprintf(stderr,"mem32: %08x %08x %08x<(%u) ",
      _MEM(3,32,2),_MEM(3,32,1),_MEM(3,32,0),interpreter.pointers[3][0]);
 
  fputc('\n',stderr);
} 

/* Opens source code file. Passing 0 takes the interpreter (with preprocessor
  bytecode) output as a source "file" (for preprocessing). */
void openIncludeFile(const char *filename)
{
  includeStackTop++;
  includeListLast++;

  if (includeStackTop >= INCLUDE_STACK_SIZE ||
    includeListLast >= INCLUDE_LIST_LEN)
  {
    printError("too many file includes");
    includeOK = 0;
  }
  else
  {
    if (filename != 0)
    {
      includeStackNames[includeStackTop][INCLUDE_FILENAME_MAX_LEN - 1] = 0; 

      for (int i = 0; i < INCLUDE_FILENAME_MAX_LEN - 1; ++i)
      {
        includeStackNames[includeStackTop][i] = filename[i];

        if (filename[i] == 0)
          break;
      }

      CMN_pseudohash('i',filename,includeListNameHashes[includeListLast]);

      for (uint8_t i = 0; i < includeListLast; ++i) // check for repeated incl.
      {
        uint8_t equals = 1;

        for (uint8_t j = 0; j < CMN_STRING_PSEUDOHASH_SIZE; ++j)
          if (includeListNameHashes[includeListLast][j] !=
              includeListNameHashes[i][j])
          {
            equals = 0;
            break;
          }

        if (equals)
        { // ignore repeated includes
          includeStackTop--;
          fprintf(stderr,WARNING_PREFIX "ignoring repeated include of \"%s\"\n",
            filename);
          return;
        }
      }
    }
    else
      CMN_pseudohash('p',"preproc",includeListNameHashes[includeListLast]);

    if (filename != 0)
    {
      includeStackFiles[includeStackTop] = fopen(filename,"r");

      if (!includeStackFiles[includeStackTop])
      {
        fprintf(stderr,ERROR_PREFIX "could not open source file \"%s\"\n",
          filename);

        includeOK = 0; 
      }
    }
    else
    { // open preprocessor output as a pseudofile
      includeStackFiles[includeStackTop] = 0;

      if (!CMN_interpreterInit(&interpreter,preprocBytecodeMem,interpreterMem,
        INTERPRETERMEM_SIZE,MIN_CELLS * stackMemMultiplier,preprocIoFunc,0,0,0))
      {
        printError("not enough memory for preproc. interpreter");
        includeOK = 0;
      }
    }

    fileJustOpened = 1;
    includeStackLines[includeStackTop] = 1;
  }

  if (!includeOK) // if error, close all alredy opened files
  {
    includeStackTop--;

    while (includeStackTop >= 0)
    {
      fclose(includeStackFiles[includeStackTop]);
      includeStackTop--;
    }
  }
}

/* Helper function, returns 0 if instruction at given address is outside any
  function, otherwise it returns the sequence number of the function plus 1. */
uint16_t bytecodeGetBlock(const uint8_t *bytecode, uint32_t n)
{
  bytecode += CMN_BYTECODE_HEADER_SIZE;

  uint16_t functionNum = 0;
  uint8_t inFunc = 0;

  while (*bytecode != CMN_OPCODE_END)
  {
    if (bytecode[0] == CMN_OPCODE_DES && bytecode[1] == CMN_DES_FUNC)
    {
      inFunc = 1;
      functionNum++;
    }

    if (n == 0)
      break;
    
    if (bytecode[0] == CMN_OPCODE_RET)
      inFunc = 0;

    bytecode += 2;

    n--;
  }

  return inFunc ? functionNum : 0;  
}

// Gets one character from the source code, handles includes etc.
char getSourceChar()
{
  if (fileJustOpened)
  {
    fileJustOpened = 0;
    return ']';
  }

  int c = 0;

  if (includeStackFiles[includeStackTop] != 0) // normal file?
    c = fgetc(includeStackFiles[includeStackTop]);
  else // interpreter output is taken instead of file
  {
    int status = CMN_INTERPRETER_OK;
 
    // TODO: some limit preventing infinite loops in interp here

    while (lastPreprocChar < 0 && status == CMN_INTERPRETER_OK)
      status = CMN_interpreterStep(&interpreter,1);

    c = status == CMN_INTERPRETER_END ? EOF : lastPreprocChar;

    lastPreprocChar = -1;
  }

  if (c == EOF || c == 0)
  {
    if (includeStackFiles[includeStackTop != 0])
      fclose(includeStackFiles[includeStackTop]);

    if (includeStackTop == 0)
      return 0;
    else
    {
      includeStackTop--;
      return '[';
    }
  }

  if (!preprocess && (c == '[' || c == ']'))
    fputs(WARNING_PREFIX "delimiter [/] found without preproc. on\n",stderr);

  if (c == '\n')
    includeStackLines[includeStackTop]++;

  return c;
}

void printSymbolTable(const CMN_Compiler *compiler)
{
  puts("SYMBOL TABLE:");

  for (uint16_t i = 0; i < compiler->symbolCount; ++i)
  {
    putchar(' '); putchar(' ');

    for (uint8_t j = 0; j < CMN_STRING_PSEUDOHASH_SIZE; ++j)
      putchar(compiler->symbolTable[i * CMN_STRING_PSEUDOHASH_SIZE + j]);

    putchar('\n');
  }
}

void printBytecodeComment(const uint8_t *instruction, FILE *f, const char *comment)
{
  if (!(*(instruction + 1) & CMN_MASK_INSTR_CON))
    fprintf(f,"   ");

  fprintf(f," # %s",comment);
}

void printBytecodeAsText(const uint8_t *bytecode, FILE *f, uint8_t comments)
{
  bytecode += CMN_BYTECODE_HEADER_SIZE;

  uint32_t addr = 0;

  while (1)
  {
    char str[16];

    CMN_instrToStr(bytecode,str);
    fprintf(f,"%06x: %s",addr,str);

    if (comments)
    {
      switch (*bytecode)
      {
        case CMN_OPCODE_DES:
          switch (*(bytecode + 1))
          {
            case CMN_DES_IF:         printBytecodeComment(bytecode,f,"if"); break;
            case CMN_DES_ELSE:       printBytecodeComment(bytecode,f,"else"); break;
            case CMN_DES_LOOP:       printBytecodeComment(bytecode,f,"loop"); break;
            case CMN_DES_IF_END:     printBytecodeComment(bytecode,f,"end if"); break;
            case CMN_DES_LOOP_END:   printBytecodeComment(bytecode,f,"end loop"); break;
            case CMN_DES_LOOP_BREAK: printBytecodeComment(bytecode,f,"break"); break;
            case CMN_DES_FUNC:       printBytecodeComment(bytecode,f,"func"); break;
            case CMN_DES_EXIT:       printBytecodeComment(bytecode,f,"exit"); break;
            case CMN_DES_LABEL:      printBytecodeComment(bytecode,f,"label"); break;
            case CMN_DES_GOTO:       printBytecodeComment(bytecode,f,"goto"); break;
            default: break;
          }
          break;

        default:
          if (*bytecode != CMN_OPCODE_COC)
          {
            uint32_t c = CMN_instrGetConst(bytecode);

            if (*bytecode == CMN_OPCODE_CON || c != 0)
            {
              printBytecodeComment(bytecode,f,"");
              fprintf(f,"%u (#%x)",c,c);
            }
          }
          break;          
      }
    }

    fputc('\n',f);
    
    if (*bytecode == CMN_OPCODE_END)
      break;

    bytecode += 2;
    addr++;
  }
}

void printInterpreterError(CMN_Interpreter *interpreter, uint8_t status)
{
  const char *errStr = "unknown";

  switch (status)
  {
    case CMN_INTERPRETER_ERROR_OPERATION: 
      errStr = "illegal operation"; break;

    case CMN_INTERPRETER_ERROR_CALLSTACK: 
      errStr = "call stack overflow"; break;

    case CMN_INTERPRETER_ERROR_ZERODIV: 
      errStr = "division by zero"; break;

    case CMN_INTERPRETER_ERROR_BAD_CALL: 
      errStr = "unknown function call"; break;

    case CMN_INTERPRETER_ERROR_MEMORY: 
      errStr = "accessing our of bounds memory"; break;

    case CMN_INTERPRETER_ERROR_STACK_OF: 
      errStr = "stack overflow"; break;
  }

  char instrStr[16];
  CMN_instrToStr(interpreter->currentInstruction,instrStr);

  fprintf(stderr,ERROR_PREFIX "runtime, (step %u, instr. 0x%lx: %s): %s\n",
    interpreter->step,(interpreter->currentInstruction - interpreter->bytecode -
      CMN_BYTECODE_HEADER_SIZE) / 2,instrStr,errStr);
}

void printCompilerError(uint8_t error)
{
  const char *errStr = "unknown";

  switch (error)
  {
    case CMN_COMPILER_ERROR_BAD_TOKEN:
      errStr = "bad token"; break;

    case CMN_COMPILER_ERROR_UNEXPECTED_TOKEN:
      errStr = "unexpected token"; break;

    case CMN_COMPILER_ERROR_UNEXPECTED_END:
      errStr = "unexpected end"; break;

    case CMN_COMPILER_ERROR_BYTECODE_TOO_BIG:
      errStr = "not enough space for bytecode"; break;

    case CMN_COMPILER_ERROR_UNKNOWN_NAME:
      errStr = "unknown name"; break;

    case CMN_COMPILER_ERROR_REDEFINED:
      errStr = "name redefined (or name hash collision)"; break;

    case CMN_COMPILER_ERROR_UNSUPPORTED:
      errStr = "not supported"; break;

    case CMN_COMPILER_ERROR_PARSE_STACK:
      errStr = "parse stack overflow"; break;

    case CMN_COMPILER_ERROR_SYMBOL_TABLE:
      errStr = "symbol table overflow"; break;

    default: break;
  }

  fprintf(stderr,ERROR_PREFIX "compiling (%s, line %u, \"%s\"): %s\n",
    includeStackNames[includeStackTop],
    includeStackLines[includeStackTop],
    compiler.tokenizer.tokenString,
    errStr);
}

void printHelp(void)
{
  puts("comun programming language (language v" CMN_LANG_VERSION_STRING
      ", library v" CMN_LIB_VERSION_STRING ")");
  puts(  "  by drummyfish, released under CC0 1.0 (public domain)");
  puts(  "  Input file with extension .cmb is considered binary bytecode.");
  puts(  "  possible options:");
  puts(  "    -h   print help and exit");
  puts(  "    -aS  pass argument S to input program");
  puts(  "    -C   transpile to C");
  puts(  "    -M   transpile to comun");
  puts(  "    -B   compile to bytecode (binary)");
  puts(  "    -T   compile to bytecode (text)");
  puts(  "    -p   enable preprocessor");
  puts(  "    -p1  output first preprocessing stage");
  puts(  "    -p2  output second preprocessing stage");
  puts(  "    -P2  output second preprocessing stage (non-minified)");
  puts(  "    -e   estimate required memory");
  puts(  "    -s   run and measure");
  puts(  "    -n   preserve similar transpiled names");
  puts(  "    -b   beautify");
  puts(  "    -m   minify");
  puts(  "    -ON  set optimization level N (0 - 3), default is 1");
  printf("    -R   increase interpreter stack memory by %d\n",MIN_CELLS);
  puts(  "    -d   debug mode");
}

uint32_t bytecodeSize(const uint8_t *bc)
{
  uint32_t result = 0;

  bc += CMN_BYTECODE_HEADER_SIZE;

  while (*bc != CMN_OPCODE_END)
  {
    bc += 2;
    result++;
  }

  return result;
}

const char *pointerIndexToName(const CMN_Compiler *compiler, uint32_t index,
  uint8_t typeEnv)
{
  if (index < 16)
  {
    _tmpName[0] = '0' + index;
    _tmpName[1] = 0;
    return _tmpName;
  }

  index -= 16;

  _tmpName[CMN_STRING_PSEUDOHASH_SIZE] = 0;

  if (compiler != 0 &&
    CMN_compilerGetSymbol(compiler,'0' + typeEnv,index,_tmpName))
    return _tmpName + 1;

  _tmpName[0] = 'p';
  _tmpName[1] = '0' + (index / 100) % 10;
  _tmpName[2] = '0' + (index / 10) % 10;
  _tmpName[3] = '0' + index % 10;
  _tmpName[4] = 0;

  return _tmpName;
}

const char *functionIndexToName(const CMN_Compiler *compiler, uint32_t index,
  uint8_t isExternal)
{
  _tmpName[CMN_STRING_PSEUDOHASH_SIZE] = 0;

  if (compiler != 0 && CMN_compilerGetSymbol(compiler,isExternal ? 'e' : 'f',
    index,_tmpName))
    return _tmpName;

  _tmpName[0] = 'f'; 

  if (!isExternal)
  {
    _tmpName[1] = 'u'; _tmpName[2] = 'n'; _tmpName[3] = 'c';
  }
  else
  {
    _tmpName[1] = 'e'; _tmpName[2] = 'x'; _tmpName[3] = 't';
  }

  _tmpName[4] = '0' + (index / 100) % 10;
  _tmpName[5] = '0' + (index / 10) % 10;
  _tmpName[6] = '0' + index % 10;
  _tmpName[7] = 0;

  return _tmpName;
}

/* Guesses if a sequence of instructions represents a push of a string literal.
  Returns length of this literal (0 means no string literal detected). */
uint16_t instructionsAreStringLit(const uint8_t *instr, uint8_t minLen)
{
  uint16_t result = 0;
  uint8_t typeEnv = CMN_instrTypeEnv(instr);

  while (instr[0] == CMN_OPCODE_CON && (instr[1] & CMN_MASK_INSTR_NOPOP) &&
    CMN_instrTypeEnv(instr) == typeEnv)
  {
    uint8_t v = CMN_instrGetConst(instr);

    if (v < ' ' || v > 126 || v == '"' || v == '[' || v == ']')
      break;

    result++;
    instr += 4;
  }

  return result >= minLen ? result : 0; 
}

int format(FILE *f, uint8_t indent, uint8_t keepComments)
{
  CMN_Tokenizer t;
  CMN_tokenizerInit(&t);

  int c = 'a';
  uint8_t spaces = 0;
  uint8_t previousToken = CMN_TOKEN_ERROR; // use error as "no value"
  uint8_t inComment = 0;

  while (c != EOF)
  {
    c = fgetc(f);
    uint8_t val = CMN_tokenizerFeedChar(&t,c != EOF ? c : 0);

    if (keepComments)
    {
      if (inComment)
      {
        if (t.state != _CMN_TOKENIZER_COMMENT) // end of comm.
        {
          inComment = 0;
          putchar('\n');
        }
        else
          putchar(c);
      }
      else if (t.state == _CMN_TOKENIZER_COMMENT) // start of comm.
      {
        inComment = 1;
        putchar('#');
      }
    }

    if (val == CMN_TOKENIZER_TOKEN)
    {
      if (indent)
      {
        val = CMN_identifyToken(t.tokenString);

        if (val == CMN_TOKEN_END || val == CMN_TOKEN_ELSE)
        {
          if (spaces < indent)
            return 0;
          else
            spaces -= indent;
        }

        if ((previousToken == CMN_TOKEN_END && // separating newline
          val != CMN_TOKEN_END && val != CMN_TOKEN_ELSE) ||
          (val == CMN_TOKEN_FUNC && previousToken != CMN_TOKEN_ERROR) ||
          ((val == CMN_TOKEN_BRANCH || val == CMN_TOKEN_LOOP) &&
          previousToken != CMN_TOKEN_FUNC && previousToken != CMN_TOKEN_ELSE &&
          previousToken != CMN_TOKEN_BRANCH && previousToken != CMN_TOKEN_LOOP))
          putchar('\n');

        for (uint8_t i = 0; i < spaces; ++i)
          putchar(' ');
      }
      else
      {
        if (previousToken != CMN_TOKEN_ERROR)
          putchar(' ');
      }

      printf("%s",t.tokenString);

      if (indent)
      {
        if (val == CMN_TOKEN_BRANCH || val == CMN_TOKEN_LOOP ||
          val == CMN_TOKEN_FUNC || val == CMN_TOKEN_ELSE)
          spaces += indent;

        putchar(indent ? '\n' : '#');
      }
        
      previousToken = val;
    }
    else if (val == CMN_TOKENIZER_ERROR)
      return 0;
  }

  return 1;
}

// include backends here so they have access to previous functions
#include "backend_c.h"
#include "backend_comun.h"

int main(int argc, char **argv)
{
  uint16_t measureSymbolCount = 0, measureCallStackMax = 0;
  uint32_t measureBCSize = 0, measureStackTopsMax[4];
  const char *pargv[MAX_ARGS];
  uint8_t pargc = 0;
  uint8_t preserveNames = 0;
  uint32_t optimizations = O1;
  uint8_t debug = 0;

  for (uint8_t i = 0; i < 4; ++i)
    measureStackTopsMax[i] = 0;

  for (uint8_t i = 1; i < argc; ++i) // load CLI arguments
  {
    const char *arg = argv[i];

    if (arg[0] == '-')
    {
      if (arg[1] == 'h' && arg[2] == 0)
      {
        printHelp();
        return 0;
      }
      else if (arg[1] == 'C' && arg[2] == 0)
        mode = MODE_C;
      else if (arg[1] == 'M' && arg[2] == 0)
        mode = MODE_COMUN;
      else if (arg[1] == 'd' && arg[2] == 0)
        debug = 1;
      else if (arg[1] == 'B' && arg[2] == 0)
        mode = MODE_BC;
      else if (arg[1] == 'T' && arg[2] == 0)
        mode = MODE_BC_TEXT;
      else if (arg[1] == 'e' && arg[2] == 0)
        mode = MODE_ESTIMATE;
      else if (arg[1] == 'p' && arg[2] == 0)
        preprocess = 1;
      else if (arg[1] == 's' && arg[2] == 0)
        mode = MODE_MEASURE;
      else if (arg[1] == 'm' && arg[2] == 0)
        mode = MODE_MINIFY;
      else if (arg[1] == 'b' && arg[2] == 0)
        mode = MODE_BEAUTIFY;
      else if (arg[1] == 'n' && arg[2] == 0)
        preserveNames = 1;
      else if (arg[1] == 'R' && arg[2] == 0)
        stackMemMultiplier++;
      else if (arg[1] == 'O' && arg[2] != 0 && arg[3] == 0)
      {
        switch (arg[2])
        {
          case '0': optimizations = 0; break;
          case '1': optimizations = O1; break;
          case '2': optimizations = O2; break;
          case '3': optimizations = O3; break;
          default: break;
        }
      }
      else if (arg[1] == 'a')
      {
        pargv[pargc] = arg + 2;
        pargc++;
      }
      else if (arg[1] == 'p' && arg[2] == '1' && arg[3] == 0)
      {
        preprocess = 1;
        mode = MODE_PREPROC1;
      }
      else if ((arg[1] == 'p' || arg[1] == 'P') && arg[2] == '2' && arg[3] == 0)
      {
        preprocess = 1;
        mode = MODE_PREPROC2;
        preprocessorMinify = arg[1] == 'p';
      }
      else
      {
        printError("bad CLI option");
        return 1;
      }
    }
    else
    {
      if (sourceFile == 0)
        sourceFile = arg;
      else
      {
        printError("multiple source files");
        return 1;
      }
    }
  }

  if (sourceFile == 0)
  {
    printError("no source file");
    return 1;
  }

  if (mode == MODE_MINIFY || mode == MODE_BEAUTIFY)
  {
    FILE *file = fopen(sourceFile,"r");

    if (!file)
    {
      printError("couldn't open input file");
      return 1;
    }

    uint8_t state =
      format(file,mode == MODE_BEAUTIFY ? 2 : 0,mode == MODE_BEAUTIFY);

    fclose (file);
    return !state;
  }

  int sourceNameLen = 0;

  while (sourceFile[sourceNameLen] != 0)
    sourceNameLen++;

  if (sourceNameLen >= 4 && sourceFile[sourceNameLen - 4] == '.' &&
    sourceFile[sourceNameLen - 3] == 'c' && sourceFile[sourceNameLen - 2] == 'm'
    && sourceFile[sourceNameLen - 1] == 'b')
  {
    // binary bytecode passed as input file

    preserveNames = 0;

    FILE *bcFile = fopen(sourceFile,"rb");

    if (!bcFile)
    {
      fprintf(stderr,ERROR_PREFIX "couldn't open bytecode \"%s\"\n",sourceFile);
      return 1;
    }

    uint32_t bcSize = fread(bytecodeMem,1,BYTECODEDMEM_SIZE,bcFile);

    if (bcSize == BYTECODEDMEM_SIZE)
    {
      char tmpChar;

      if (fread(&tmpChar,1,1,bcFile))
      {
        fclose(bcFile);
        printError("input bytecode too large");
        return 1;
      }
    }

    uint8_t sanity = CMN_bytecodeCheckSanity(bytecodeMem,bcSize);

    if (sanity != CMN_BYTECODE_SANITY_OK)
    {
      const char *errStr = "";

      switch (sanity)
      {
        case CMN_BYTECODE_SANITY_ERROR_HEADER: errStr = " (bad header)"; break;
        case CMN_BYTECODE_SANITY_ERROR_CHECKSUM: errStr = " (checksum)"; break;
        case CMN_BYTECODE_SANITY_ERROR_INSTR: errStr = " (bad instr.)"; break;
        case CMN_BYTECODE_SANITY_ERROR_NO_END: errStr = " (no END)"; break;
        default: break;
      }

      fprintf(stderr,"ERROR: malformed bytecode%s\n",errStr);
      return 1;
    }
  }
  else // normal text source code file as input
  {
    openIncludeFile(sourceFile);

    if (preprocess)
    {
      if (debug)
        printDebug("preprocessing");

      CMN_compilerInit(&compiler,preprocBytecodeMem,BYTECODEDMEM_SIZE,symbolMem,
        SYMBOLMEM_SIZE,openIncludeFile);

      CMN_preprocessorInit(&preprocessor,preprocessorMinify,
        mode == MODE_PREPROC1 ? preprocessorToOut : preprocessorToCompiler);

      while (1)
      {
        if (!includeOK)
          return 1;

        char sourceChar = getSourceChar();

        if (CMN_preprocessorFeedChar(&preprocessor,sourceChar)
          == CMN_PREPROCESSOR_ERROR)
        {
          printError("preproc.: bad token in underlying code");
          return 1;
        }

        if (compiler.state != CMN_COMPILER_OK)
        {
          printError("preproc.: compiler error");
          printCompilerError(compiler.state);
          return 1;
        }

        if (sourceChar == 0)
          break;
      }
    }

    if (mode == MODE_PREPROC1)
      return 0;

    if (preprocess)
      openIncludeFile(0); // open the interpreter output as a source file

    if (mode == MODE_PREPROC2)
    {
      while (1)
      {
        char c = getSourceChar();

        if (c)
          putchar(c);
        else
          break;
      }    

      return 0;
    }

    CMN_compilerInit(&compiler,bytecodeMem,BYTECODEDMEM_SIZE,symbolMem,
      SYMBOLMEM_SIZE,openIncludeFile);

    if (debug)
      printDebug("compiling");

    while (compiler.state == CMN_COMPILER_OK)
    {
      if (!includeOK)
        return 1;

      char sourceChar = getSourceChar();

      CMN_compilerFeedChar(&compiler,sourceChar);

      if (mode == MODE_MEASURE)
      {
        if (compiler.symbolCount > measureSymbolCount)
          measureSymbolCount = compiler.symbolCount;
      }

      if (sourceChar == 0)
        break;
    }

    if (compiler.state != CMN_COMPILER_OK)
    {
      printCompilerError(compiler.state);
      return 1;
    }
  } // if (normal text source code)

  if (mode == MODE_MEASURE)
    measureBCSize = bytecodeSize(bytecodeMem);

  if (debug && optimizations != 0)
    printDebug("optimizing");

  CMN_bytecodeOptimize(bytecodeMem,optimizations,&compiler);

  switch (mode)
  {
    case MODE_RUN:
    case MODE_MEASURE:
    {
      if (debug)
        printDebug("running");

      if (!CMN_interpreterInit(&interpreter,bytecodeMem,interpreterMem,
        INTERPRETERMEM_SIZE,MIN_CELLS * stackMemMultiplier,ioFunc,0,pargc,pargv))
      {
        printError("not enough memory for interpreter");
        return 1;
      }

      while (1)
      {
        int status = CMN_interpreterStep(&interpreter,1);

        if (status != CMN_INTERPRETER_OK)
        {
          if (status != CMN_INTERPRETER_END)
          {
            printInterpreterError(&interpreter,status);
            return 1;
          }

          break;
        }

        if (debug)
          interpreterDebugPrint();

        if (mode == MODE_MEASURE)
        {
          if (interpreter.callStackTop > measureCallStackMax)
            measureCallStackMax = interpreter.callStackTop;

          for (uint8_t i = 0; i < 4; ++i)
            if (interpreter.pointers[i] != 0 &&
              interpreter.pointers[i][0] > measureStackTopsMax[i])
              measureStackTopsMax[i] = interpreter.pointers[i][0];
        }
      }

      if (mode == MODE_MEASURE)
      {
        puts("\n\nmeasured metrics:");
        printf("  bytecode instructions (orig., optim.): %u, %u\n",
          measureBCSize,bytecodeSize(bytecodeMem));
        printf("  symbols: %d\n",measureSymbolCount);
        printf("  executed steps: %u\n",interpreter.step);
        printf("  max call stack depth: %d\n",measureCallStackMax);
        printf("  max stack top (0, 8, 16, 32): %d, %d, %d, %d\n",
          measureStackTopsMax[0],measureStackTopsMax[1],measureStackTopsMax[2],
          measureStackTopsMax[3]);
      }

      break;
    }

    case MODE_BC:
    {
      uint32_t i = 0;

      while (i < CMN_BYTECODE_HEADER_SIZE || bytecodeMem[i] != CMN_OPCODE_END)
      {
        putchar(bytecodeMem[i]);
        putchar(bytecodeMem[i + 1]);

        i += 2;
      }

      putchar(0);
      putchar(0);
      break;
    }

    case MODE_BC_TEXT:
      printBytecodeAsText(bytecodeMem,stdout,1);
      break;

    case MODE_C:
      if (!printBytecodeAsC(bytecodeMem,stdout,preserveNames ? &compiler : 0))
      {
        printError("can't transpile this code");
        return 1;
      }
      break;

    case MODE_COMUN:
      if (!printBytecodeAsComun(bytecodeMem,stdout,preserveNames ?
        &compiler : 0))
      {
        printError("can't transpile this code");
        return 1;
      }
      break;

    case MODE_ESTIMATE:
    {
      uint32_t mems[4], ptrs[4];

      CMN_estimateMemory(bytecodeMem,MIN_CELLS * stackMemMultiplier,mems,ptrs);
      
      for (uint8_t i = 0; i < 4; ++i)
        printf("env %d: %d cells, %d pointers\n",i,mems[i],ptrs[i]);

      break;
    }

    default: break;
  }

  if (debug)
    printDebug("end");

  return 0;
}
